/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

 ****/
package org.activequant.broker;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.events.OrderCancelEvent;
import org.activequant.core.domainmodel.events.OrderCompletionEvent;
import org.activequant.core.domainmodel.events.OrderEvent;
import org.activequant.core.domainmodel.events.OrderUpdateEvent;
import org.activequant.core.types.OrderSide;
import org.activequant.core.types.OrderType;
import org.activequant.core.types.TimeStamp;
import org.activequant.data.retrieval.IQuoteSubscriptionSource;
import org.activequant.data.retrieval.ISubscription;
import org.activequant.util.pattern.events.Event;
import org.activequant.util.pattern.events.IEventListener;
import org.activequant.util.pattern.events.IEventSource;

/**
 * Proxy broker that simulates trailing stop orders via stop order, that is periodically updated
 * as price quote changes. This object needs quote subscription feed.<br/>
 * An IBroker implementation. Defines the OrderTracker(implements IOrderTracker) class. Holds the following associated variables:
 * <ul>
 * <li>engine(IBroker)</li>
 * <li>subscriptionSource(IQuoteSubscriptionSource)</li>
 * <li>managedOrders(Map&lt;IOrderTracker,OrderTracker&gt;)</li>
 * </ul>
 * <p>
 * <b>History:</b><br>
 *  - [13.12.2007] Created (Mike Kroutikov)<br>
 *
 *  @author Mike Kroutikov
 */
public class TrailingStopSimulatingBrokerProxy implements IBroker {
	/**
	 * private final IBroker engine;
	 */
	private final IBroker engine;
	/**
	 * private final IQuoteSubscriptionSource subscriptionSource;
	 */
	private final IQuoteSubscriptionSource subscriptionSource;
	/**
	 * constructs a TrailingStopSimulatingBrokerProxy(implements IBroker) using the given engine(IBroker) and subscriptionSource(IQuoteSubscriptionSource)
	 * to set its associated engine(IBroker) and subscriptionSource(IQuoteSubscriptionSource)
	 * @param engine
	 * @param subscriptionSource
	 */
	public TrailingStopSimulatingBrokerProxy(IBroker engine, IQuoteSubscriptionSource subscriptionSource) {
		this.engine = engine;
		this.subscriptionSource = subscriptionSource;
	}
	/**
	 * private static final double EPSILON = 1e-6;
	 */
	private static final double EPSILON = 1e-6;
	/**
	 * Returns a stopOrder(Order) whose orderTimeStamp(TimeStamp), instrumentSpecification(InstrumentSpecification), orderSide(OrderSide) and quantity(double) are copied from the 
	 * given trailingOrder(Order). It's orderType is OrderType.STOP and its stopPrice(double) is calculated using the given price(double) as <code>price - trailingOrder.getTrailingDistance()</code> (for a SELL order)
	 * or <code>price + trailingOrder.getTrailingDistance()</code> (for a BUY order)
	 * @param trailingOrder
	 * @param price
	 * @return
	 */
	private static Order convertToStopOrder(Order trailingOrder, double price) {
		Order stopOrder = new Order();
		stopOrder.setOrderTimeStamp(trailingOrder.getOrderTimeStamp());
		stopOrder.setInstrumentSpecification(trailingOrder.getInstrumentSpecification());
		stopOrder.setOrderSide(trailingOrder.getOrderSide());
		stopOrder.setOrderType(OrderType.STOP);
		stopOrder.setQuantity(trailingOrder.getQuantity());
		if(stopOrder.getOrderSide() == OrderSide.SELL) {
			stopOrder.setStopPrice(price - trailingOrder.getTrailingDistance());
		} else {
			stopOrder.setStopPrice(price + trailingOrder.getTrailingDistance());
		}
		return stopOrder;
	}
	/**
	 * An IOrderTracker implementation. Holds the following associated variables:
	 * <ul>
	 * <li>order(Order)</li>
	 * <li>stopTracker(IOrderTracker)</li>
	 * <li>subscription(ISubscription&lt;Quote&gt;)</li>
	 * <li>updatedOrder(Order)</li>
	 * <li>updatedStopOrder(Order)</li>
	 * <li>orderEvent(Event&lt;OrderEvent&gt;)</li>
	 * </ul>
	 * @author Dimitar
	 *
	 */
	class OrderTracker implements IOrderTracker {
		/**
		 * private final Order order;
		 */
		private final Order order;
		/**
		 * private IOrderTracker stopTracker;
		 */
		private IOrderTracker stopTracker;
		/**
		 * private ISubscription&lt;Quote&gt; subscription;
		 */
		private ISubscription<Quote> subscription;
		/**
		 * private Order updatedOrder;<br/>
		 * trail-stop order
		 */
		private Order updatedOrder; // trail-stop order
		/**
		 * private Order updatedStopOrder;<br/>
		 * most recent view of the stopOrder
		 */
		private Order updatedStopOrder; // most recent view of the stopOrder
		/**
		 * private final Event&lt;OrderEvent&gt; orderEvent = new Event&lt;OrderEvent&gt;();
		 */
		private final Event<OrderEvent> orderEvent = new Event<OrderEvent>();
		/**
		 * constructs an OrderTracker(implements IOrderTracker) using the given order(Order) to set its associated order(Order) and updatedOrder(Order)
		 * @param order
		 */
		public OrderTracker(Order order) {
			this.order = this.updatedOrder = order;
		}
		/**
		 * <strong>1.</strong> If the associated stopTracker(IOrderTracker) is null, it creates and fires a simulated OrderCancelEvent onto the associated orderEvent(Event&lt;OrderEvent&gt;).
		 * Otherwise it invokes <code>stopTracker.cancel()</code> on the associated stopTracker(IOrderTracker).<br/>
		 * <strong>2.</strong> Cancels the associated subscription(ISubscription&lt;Quote&gt;)
		 */
		public void cancel() {
			if(stopTracker == null) {
				// simulate success by firing the event
				OrderCancelEvent cancelEvent = new OrderCancelEvent();
				cancelEvent.setEventTimeStamp(new TimeStamp()); // FIXME: need market time
				cancelEvent.setMessage("order canceled (internally)");
				try {
					orderEvent.fire(cancelEvent);
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else {
				stopTracker.cancel();
			}
			subscription.cancel();
		}
		/**
		 * returns the OrderCompletionEvent from the associated stopTracker(IOrderTracker)
		 */
		public OrderCompletionEvent getOrderCompletion() {
			return stopTracker.getOrderCompletion();
		}
		/**
		 * returns the associated orderEvent(Event&lt;OrderEvent&gt;)
		 */
		public IEventSource<OrderEvent> getOrderEventSource() {
			return orderEvent;
		}
		/**
		 * returns the associated order(Order)
		 */
		public Order getOrder() {
			return order;
		}
		/**
		 * returns the brokerAssignedId(String) from the associated stopTracker(IOrderTracker)
		 */
		public String getBrokerAssignedId() {
			return stopTracker.getBrokerAssignedId();
		}
		/**
		 * sets and activates the associated subscription(ISubscription&lt;Quote&gt;) with a subscription for the instrumentSpecification(InstrumentSpecification) of the associated
		 * order(Order) (by invoking <code>subscriptionSource.subscribe(order.getInstrumentSpecification())</code> on the associated subscriptionSource(IQuoteSubscriptionSource)).<br/>
		 * It also attaches an IEventListener&lt;Quote&gt; which will set and submit the Stop order and then keeps updating the stopPrice as necessary. 
		 */
		public void submit() {
			try {
				subscription = subscriptionSource.subscribe(order.getInstrumentSpecification());
				subscription.addEventListener(new IEventListener<Quote>() {
					public void eventFired(Quote event) {
						checkOrder(event);
					}
				});
				subscription.activate();
			} catch(Exception ex) {
				throw new RuntimeException(ex);
			}
		}
		/**
		 * <strong>1.</strong> Looks at the askPrice(double) or bidPrice(double) (depending on whether the associated order(Order) is BUY or not). The relevant price has to be
		 * greater than 0 otherwise it returns without doing anything.<br/>
		 * <strong>2.</strong> If the associated updatedStopOrder(Order) is still null, it will initialize it with a copy of the associated order(Order) whose OrderType=OrderType.STOP
		 * and whose stopPrice(double) is calculated using the relevant price(double)+/-order.trailingDistance. It also sets/initializes the associated stopTracker(IOrderTracker)
		 * by invoking <code>engine.prepareOrder(updatedStopOrder)</code> and then <code>stopTracker.submit()</code> to submit the Stop order. It also registers a listener to keep
		 * updating the order and then cancel the subscription once it is completed.<br/>
		 * <strong>3.</strong> Otherwise, it checks whether the stopPrice(double) of the associated updatedStopOrder(Order) needs to get updated, and if so, it invokes 
		 * <code>stopTracker.update(newOrder)</code> with the updated stopPrice
		 * @param quote
		 */
		private void checkOrder(Quote quote) {
			double price = order.getOrderSide() == OrderSide.BUY ? quote.getAskPrice() : quote.getBidPrice();
			if(price <= 0.) {
				return;
			}

			if(updatedStopOrder == null) {
				// real order not yet submitted to the backend (because price was not known)
				updatedStopOrder = convertToStopOrder(order, price);
				stopTracker = engine.prepareOrder(updatedStopOrder);
	
				stopTracker.getOrderEventSource().addEventListener(new IEventListener<OrderEvent>() {
					public void eventFired(OrderEvent event) {
						if(event instanceof OrderCompletionEvent) {
							subscription.cancel();
						} else if(event instanceof OrderUpdateEvent) {
							updatedStopOrder = ((OrderUpdateEvent) event).getUpdatedOrder();
							// translate update info from stop to trailing stop.
							OrderUpdateEvent ue = new OrderUpdateEvent(event.getEventTimeStamp(), event.getMessage(), updatedOrder);
							orderEvent.fire(ue);
							return;
						}
						orderEvent.fire(event);
					}
				});
				stopTracker.submit();
				return;
			}

			if(updatedOrder.getOrderSide() == OrderSide.BUY) {
				if(price < updatedStopOrder.getStopPrice() - updatedOrder.getTrailingDistance()) {
					Order newOrder = new Order(updatedStopOrder);
					newOrder.setStopPrice(price + updatedOrder.getTrailingDistance());
					stopTracker.update(newOrder);
				}
			} else {
				if(price > updatedStopOrder.getStopPrice() + updatedOrder.getTrailingDistance()) {
					Order newOrder = new Order(updatedStopOrder);
					newOrder.setStopPrice(price - updatedOrder.getTrailingDistance());
					stopTracker.update(newOrder);
				}
			}
		}
		/**
		 * Sets/updates the associated updatedOrder(Order) with a copy of the given newOrder(Order). Only trailing distance can be updated for sure.
		 * The quantity(double) can be updated only if the new and old quantity differ for an amount larger than EPSILON(1e-6). In that case it will also invoke
		 * <code>stopTracker.update(updatedOrder)</code> on the associated stopTracker(IOrderTracker)
		 */
		public void update(Order newOrder) {
			// only trailing distance and quantity can be updated
			double quantityDiff = newOrder.getQuantity() - updatedOrder.getQuantity();

			updatedOrder = new Order(newOrder);
			if(Math.abs(quantityDiff) > EPSILON) {
				updatedOrder.setQuantity(newOrder.getQuantity());
				stopTracker.update(updatedOrder);
			}
		}
	}
	/**
	 * If the OrderType of the given order(Order) is OrderType.TRAILING_STOP, it creates and returns an OrderTracker(implements IOrderTracker) using 
	 * the given order(Order) to set its associated order(Order) and updatedOrder(Order).<br/>
	 * Otherwise, it uses the associated engine(IBroker) to validate, prepare the given order(Order) for submission and return an IOrderTracker
	 */
	public IOrderTracker prepareOrder(Order order) {
		if(order.getOrderType() == OrderType.TRAILING_STOP) {
			// intercept. place stop order with the engine
			return new OrderTracker(order);
		} else {
			// do not care, just return the original
			return engine.prepareOrder(order);
		}
	}
	/**
	 * private final Map&lt;IOrderTracker,OrderTracker&gt; managedOrders = new IdentityHashMap&lt;IOrderTracker,OrderTracker&gt;();
	 */
	private final Map<IOrderTracker,OrderTracker> managedOrders = new IdentityHashMap<IOrderTracker,OrderTracker>(); 
	/**
	 * returns an IOrderTracker[] holding all orders kept in the associated engine(IBroker)
	 */
	public IOrderTracker[] getOrders() {
		List<IOrderTracker> list = new ArrayList<IOrderTracker>();
		
		for(IOrderTracker t : engine.getOrders()) {
			if(managedOrders.containsKey(t)) {
				list.add(managedOrders.get(t));
			} else {
				list.add(t);
			}
		}

		return list.toArray(new IOrderTracker[0]);
	}
}
